﻿using Microsoft.Extensions.Options;

namespace FaceService.Infrastructure.Service;

public class FIService : IFIService
{
    private readonly FaceDbContext _db;
    private readonly IDistributedCacheHelper _cacheHelper;
    private readonly IFace _faceHelper;
    private readonly IOptionsSnapshot<FaceStorageOptions> _options;

    public FIService(FaceDbContext db, IDistributedCacheHelper cacheHelper, IFace faceHelper, IOptionsSnapshot<FaceStorageOptions> options)
    {
        _db = db;
        _cacheHelper = cacheHelper;
        _faceHelper = faceHelper;
        _options = options;
    }

    public async Task<FaceInfoReponse?> GetByIdAsync(Guid faceId)
    {
        FaceInfoReponse? result = await _cacheHelper.GetOrCreateAsync($"FaceService_Face_Id_{faceId}", async (_) =>
        {
            return await _db.Faces
                .Select(p => new FaceInfoReponse(p.Id, p.Name, p.Image, p.StudentId))
                .AsNoTracking()
                .FirstOrDefaultAsync(row => row.Id == faceId);
        });
        return result;
    }

    public async Task<List<FaceInfoReponse>?> GetAllAsync(string search, int pageNo, int pageSize)
    {
        return await _db.Faces
            .AsNoTracking()
            .Where(p => p.Name.Contains(search) || p.StudentId.Contains(search))
            .OrderBy(p => p.Id)
            .Select(p => new FaceInfoReponse(p.Id, p.Name, p.Image, p.StudentId))
            .Skip((pageNo - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
    }

    public async Task<long> GetAllTotalAsync(string search)
    {
        return await _db.Faces
            .AsNoTracking()
            .Where(p => p.Name.Contains(search) || p.StudentId.Contains(search))
            .LongCountAsync();
    }

    public async Task<FaceInfoReponse?> CreatedAsync(string name, string image, string studentId)
    {
        List<float> faceExtract = await _faceHelper.GetExtract(image);
        Face face = Face.Created(Guid.NewGuid(), name, image, studentId, faceExtract);
        await _db.Faces.AddAsync(face);
        await _db.SaveChangesAsync();
        FaceInfoReponse result = new FaceInfoReponse(face.Id, face.Name, face.Image, face.StudentId);
        return result;
    }

    public async Task<Guid?> DeletedByIdAsync(Guid id)
    {
        await _cacheHelper.RemoveAsync($"FaceService_Face_Id_{id}");
        Face? face = await _db.Faces.FirstOrDefaultAsync(row => row.Id == id);
        if (face is null) return null;
        face.Deleted();
        await _db.SaveChangesAsync();
        return face.Id;
    }

    public async Task<FaceInfoReponse?> UpdatedAsync(Guid id, string? name, string? image, string? studentId)
    {
        await _cacheHelper.RemoveAsync($"FaceService_Face_Id_{id}");
        Face? face = await _db.Faces.FirstOrDefaultAsync(row => row.Id == id);
        if (face is null) return null;
        if (name is not null) face.ChangeName(name);
        if (image is not null)
        {
            face.ChangeImage(image);
            List<float> faceExtract = await _faceHelper.GetExtract(image);
            face.ChangeFaceExtract(faceExtract);
        }
        if (studentId is not null) face.ChangeStudentId(studentId);
        await _db.SaveChangesAsync();
        return new FaceInfoReponse(face.Id, face.Name, face.Image, face.StudentId);
    }

    public async Task<List<SearchFaceReponse>> SearchFaceAsync(string searchFaceUrl)
    {
        List<SearchFaceReponse> result = new();
        var extract = await _faceHelper.GetExtract(searchFaceUrl);
        var faceList = await _db.Faces.AsNoTracking().ToListAsync();
        float similarity = _options.Value.Similarity;

        foreach (var face in faceList)
        {
            float compare = _faceHelper.Compare(extract, face.FaceExtract);
            if (compare > similarity)
            {
                result.Add(new SearchFaceReponse(face.Id, face.Name, face.Image, face.StudentId, compare));
            }
        }

        return new List<SearchFaceReponse>(result.OrderByDescending(p => p.Compare));
    }
}